package com.yeskery.nut.cloud.registry.inlay;

import com.yeskery.nut.application.ServerEventContext;
import com.yeskery.nut.bean.ApplicationContext;
import com.yeskery.nut.bean.aware.ApplicationContextAware;
import com.yeskery.nut.cloud.registry.core.*;
import com.yeskery.nut.core.*;
import com.yeskery.nut.extend.responsive.ResponsiveConvert;
import com.yeskery.nut.util.StringUtils;
import com.yeskery.nut.util.http.HttpUtils;

import java.security.SecureRandom;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

/**
 * 默认内置注册器
 * @author YESKERY
 * 2024/1/4
 */
public class InlayServerRegistry extends DefaultServerRegistry implements ApplicationContextAware {

    /** 日志对象 */
    private static final Logger logger = Logger.getLogger(InlayServerRegistry.class.getName());

    /** 同步Key */
    private static final String ASYNC_KEY = "Server-Registry";

    /** 同步value */
    private static final String ASYNC_VALUE = "InlayServerRegistry";

    /** 同步推送uri */
    private static final String ASYNC_PUSH_URI = "/inlay-server-registry/async/push";

    /** 同步拉取uri */
    private static final String ASYNC_PULL_URI = "/inlay-server-registry/async/pull";

    /** 服务地址key */
    private static final String SERVER_ADDRESS_KEY = "nut.cloud.registry.inlay.uri";

    /** 应用上下文 */
    private ApplicationContext applicationContext;

    /** 定时器 */
    private Timer timer;

    /** 当前的服务实例 */
    private Server server;

    /** 服务地址集合 */
    private final List<String> serverAddressList = new ArrayList<>();

    @Override
    public void afterStart(ServerEventContext serverEventContext) {
        Environment environment = applicationContext.getBean(Environment.class);
        String serverAddress = environment.getEnvProperty(SERVER_ADDRESS_KEY);
        if (!StringUtils.isEmpty(serverAddress)) {
            List<String> addressList = Arrays.stream(serverAddress.split(","))
                    .filter(s -> !StringUtils.isEmpty(s)).collect(Collectors.toList());
            if (!addressList.isEmpty()) {
                serverAddressList.addAll(addressList);
            }
        }
        Server currentServer = registerServer(getCurrentServerMetadata(environment));

        currentServer.getServerInstanceRegistry().registerInstance(getCurrentServerInstanceMetadata(environment));
        server = currentServer;

        ControllerManager controllerManager = serverEventContext.getControllerManager();
        controllerManager.registerController(new InlayServerRegistryAsyncPushRequestHandler(applicationContext), Method.PUT, ASYNC_PUSH_URI);
        logger.info("InlayServerRegistry: Async Push Endpoint[" + ASYNC_PUSH_URI + "], Alias[], Method[" + Method.PUT + "]");
        controllerManager.registerController(new InlayServerRegistryAsyncPullRequestHandler(applicationContext), Method.POST, ASYNC_PULL_URI);
        logger.info("InlayServerRegistry: Async Pull Endpoint[" + ASYNC_PULL_URI + "], Alias[], Method[" + Method.POST + "]");
        timer = new Timer("inlayServerRegistryAsyncTimer", true);
        timer.schedule(new InlayServerAsyncPushTimerTask(applicationContext), 30000, 90000);
        timer.schedule(new InlayServerAsyncPullTimerTask(applicationContext), 0, 60000);
    }

    @Override
    public void beforeClose(ServerEventContext serverEventContext) {
        super.beforeClose(serverEventContext);
        if (timer != null) {
            timer.cancel();
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    /**
     * 默认内置同步推送请求处理类
     * @author YESKERY
     * 2024/1/5
     */
    private class InlayServerRegistryAsyncPushRequestHandler implements RequestHandler {

        /** 应用上下文 */
        private final ApplicationContext applicationContext;

        /**
         * 构建默认内置同步推送请求处理类
         * @param applicationContext 应用上下文
         */
        private InlayServerRegistryAsyncPushRequestHandler(ApplicationContext applicationContext) {
            this.applicationContext = applicationContext;
        }

        @Override
        public void handle(Request request, Response response, Execution execution) {
            String value = request.getHeader(ASYNC_KEY);
            if (StringUtils.isEmpty(value) || !ASYNC_VALUE.equals(value)) {
                return;
            }
            ResponsiveConvert responsiveConvert = applicationContext.getBean(ResponsiveConvert.class);
            InlayServersAsync inlayServersAsync = responsiveConvert.convertFromString(request.getBodyAsString(), InlayServersAsync.class);
            refreshInlayServersAsync(inlayServersAsync);
        }
    }

    /**
     * 默认内置同步拉取请求处理类
     * @author YESKERY
     * 2024/1/5
     */
    private class InlayServerRegistryAsyncPullRequestHandler implements RequestHandler {

        /**
         * 应用上下文
         */
        private final ApplicationContext applicationContext;

        /**
         * 构建默认内置同步推送请求处理类
         *
         * @param applicationContext 应用上下文
         */
        private InlayServerRegistryAsyncPullRequestHandler(ApplicationContext applicationContext) {
            this.applicationContext = applicationContext;
        }

        @Override
        public void handle(Request request, Response response, Execution execution) {
            String value = request.getHeader(ASYNC_KEY);
            if (StringUtils.isEmpty(value) || !ASYNC_VALUE.equals(value)) {
                return;
            }
            response.writeJson(parseInlayServersAsyncToJson(applicationContext.getBean(ResponsiveConvert.class)));
        }
    }

    /**
     * 默认内置同步推送定时任务
     * @author YESKERY
     * 2024/1/5
     */
    private class InlayServerAsyncPushTimerTask extends TimerTask {

        /** 要访问的服务大小 */
        private static final int ACCESS_SIZE = 3;

        /** 随机数 */
        private final SecureRandom random = new SecureRandom();

        /** 应用上下文 */
        private final ApplicationContext applicationContext;

        /**
         * 构建默认内置同步推送定时任务
         * @param applicationContext 应用上下文
         */
        private InlayServerAsyncPushTimerTask(ApplicationContext applicationContext) {
            this.applicationContext = applicationContext;
        }

        @Override
        public void run() {
            List<Server> servers = new ArrayList<>(getServerMap().values());
            if (servers.isEmpty()) {
                return;
            }

            List<Server> accessServers;
            if (servers.size() <= ACCESS_SIZE) {
                accessServers = servers.stream().filter(r -> r != InlayServerRegistry.this.server)
                        .collect(Collectors.toList());
            } else {
                Collections.shuffle(servers, random);
                accessServers = servers.stream().filter(r -> r != InlayServerRegistry.this.server)
                        .limit(ACCESS_SIZE).collect(Collectors.toList());
            }

            for (Server server : accessServers) {
                for (Instance instance : server.getInstances()) {
                    String url = instance.getScheme() + instance.getIp() + ":" + instance.getPort() + ASYNC_PUSH_URI;
                    try {
                        HttpUtils.getDefaultInstance().doPut(url, Collections.singletonList(new NameAndValue(ASYNC_KEY, ASYNC_VALUE)),
                                parseInlayServersAsyncToJson(applicationContext.getBean(ResponsiveConvert.class)), MediaType.APPLICATION_JSON);
                    } catch (Exception e) {
                        logger.logp(Level.INFO, "InlayServerRegistry.InlayServerAsyncPushTimerTask", "run",
                                "InlayServerRegistryAsync Server[" + server.name() + "], Instance[scheme(" 
                                + instance.getScheme() + "), ip(" + instance.getIp() + "), port(" + instance.getPort() 
                                + ") Async Push Fail.", e);
                    }
                }
            }
        }
    }

    /**
     * 默认内置同步拉取定时任务
     * @author YESKERY
     * 2024/1/5
     */
    private class InlayServerAsyncPullTimerTask extends TimerTask {

        /** 应用上下文 */
        private final ApplicationContext applicationContext;

        /**
         * 构建默认内置同步拉取定时任务
         * @param applicationContext 应用上下文
         */
        private InlayServerAsyncPullTimerTask(ApplicationContext applicationContext) {
            this.applicationContext = applicationContext;
        }

        @Override
        public void run() {
            ResponsiveConvert responsiveConvert = applicationContext.getBean(ResponsiveConvert.class);
            for (String serverAddress : serverAddressList) {
                try {
                    String result = HttpUtils.getDefaultInstance().doPostString(serverAddress + ASYNC_PULL_URI,
                            Collections.singletonList(new NameAndValue(ASYNC_KEY, ASYNC_VALUE)), (String) null, MediaType.APPLICATION_JSON);
                    if (!StringUtils.isEmpty(result)) {
                        InlayServersAsync inlayServersAsync = responsiveConvert.convertFromString(result, InlayServersAsync.class);
                        refreshInlayServersAsync(inlayServersAsync);
                    }
                } catch (Exception e) {
                    logger.logp(Level.INFO, "InlayServerRegistry.InlayServerAsyncPullTimerTask", "run",
                            "InlayServerRegistryAsync Server Address[" + serverAddress + " Async Pull Fail.", e);
                }
            }
        }
    }

    /**
     * 转换服务为内置服务同步对象集合对象json
     * @param responsiveConvert 响应式转换器
     * @return 转换后的服务为内置服务同步对象集合对象json
     */
    private String parseInlayServersAsyncToJson(ResponsiveConvert responsiveConvert) {
        Collection<Server> serverCollection = getServerMap().values();
        InlayServersAsync inlayServersAsync = new InlayServersAsync();
        List<InlayServerAsync> inlayServerAsyncList = new ArrayList<>(serverCollection.size());
        for (Server server : serverCollection) {
            inlayServerAsyncList.add(parseInlayServerAsync(server));
        }
        inlayServersAsync.setServers(inlayServerAsyncList);
        return responsiveConvert.convertTo(inlayServersAsync);
    }

    /**
     * 转换内置服务同步对象
     * @param server 服务对象
     * @return 转换后的内置服务同步对象
     */
    private InlayServerAsync parseInlayServerAsync(Server server) {
        InlayServerAsync inlayServerAsync = new InlayServerAsync();
        inlayServerAsync.setName(server.name());
        inlayServerAsync.setVersion(server.getMetadata().getVersion());
        List<InlayServerInstanceAsync> inlayServerInstanceAsyncList = parseInlayServerInstanceAsync(server.getInstances());
        inlayServerAsync.setInstances(inlayServerInstanceAsyncList);
        return inlayServerAsync;
    }

    /**
     * 转换内置服务实例同步对象
     * @param instances 服务实例集合
     * @return 转换后的内置服务实例同步对象
     */
    private List<InlayServerInstanceAsync> parseInlayServerInstanceAsync(List<Instance> instances) {
        List<InlayServerInstanceAsync> inlayServerInstanceAsyncList = new ArrayList<>(instances.size());
        for (Instance instance : instances) {
            InlayServerInstanceAsync inlayServerInstanceAsync = new InlayServerInstanceAsync();
            inlayServerInstanceAsync.setIp(instance.getIp());
            inlayServerInstanceAsync.setScheme(instance.getScheme());
            inlayServerInstanceAsync.setPort(instance.getPort());
            inlayServerInstanceAsyncList.add(inlayServerInstanceAsync);
        }
        return inlayServerInstanceAsyncList;
    }

    /**
     * 刷新内置服务同步对象集合
     * @param inlayServersAsync 内置服务同步对象集合
     */
    private void refreshInlayServersAsync(InlayServersAsync inlayServersAsync) {
        for (InlayServerAsync inlayServerAsync : inlayServersAsync.getServers()) {
            String serverName = inlayServerAsync.getName();
            Server server = getServer(serverName);
            if (server == null) {
                DefaultServerMetadata defaultServerMetadata = new DefaultServerMetadata();
                defaultServerMetadata.setName(serverName);
                defaultServerMetadata.setRegisterTime(System.currentTimeMillis());
                defaultServerMetadata.setVersion(inlayServerAsync.getVersion());
                defaultServerMetadata.setLatestUpdateTime(defaultServerMetadata.getRegisterTime());
                registerServer(defaultServerMetadata);
                server = getServer(serverName);
            } else {
                DefaultServerMetadata defaultServerMetadata = (DefaultServerMetadata) server.getMetadata();
                String version;
                if ((version = server.getMetadata().getVersion()) != null && version.equals(defaultServerMetadata.getVersion())) {
                    logger.info("InlayServerRegistryAsync Server[" + serverName + "] Original Version["
                            + inlayServerAsync.getVersion() + "] Local Version[" + defaultServerMetadata.getVersion()
                            + "] Not Match, Skip Update.");
                    continue;
                }
                defaultServerMetadata.setLatestUpdateTime(System.currentTimeMillis());
            }

            ServerInstanceRegistry serverInstanceRegistry = server.getServerInstanceRegistry();
            for (InlayServerInstanceAsync inlayServerInstanceAsync : inlayServerAsync.getInstances()) {
                DefaultServerInstanceMetadata defaultServerInstanceMetadata = new DefaultServerInstanceMetadata();
                defaultServerInstanceMetadata.setScheme(inlayServerInstanceAsync.getScheme());
                defaultServerInstanceMetadata.setIp(inlayServerInstanceAsync.getIp());
                defaultServerInstanceMetadata.setPort(inlayServerInstanceAsync.getPort());
                if (!serverInstanceRegistry.contains(defaultServerInstanceMetadata)) {
                    defaultServerInstanceMetadata.setStatus(ServerInstanceStatus.OK);
                    serverInstanceRegistry.registerInstance(defaultServerInstanceMetadata);
                }
            }
        }
    }
}
